package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"time"

	"github.com/hyperledger/fabric-contract-api-go/contractapi"
	"github.com/xlcetc/cryptogm/sm/sm3"
)

// SmartContract provides functions for managing an Asset
type SmartContract struct {
	contractapi.Contract
}

type UserInfo struct {
	UserID   string `json:"user_id"`
	UserName string `json:"user_name"`
	PassWord string `json:"pass_word"`
	Token    string `json:"token"`
}

type RespLogin struct {
	UserName string `json:"user_name"`
	Token    string `json:"token"`
}

type Token struct {
	UserID     string `json:user_id`
	ExpireDate int64  `json:expire_date`
}

const EXPIRE_DURATION int64 = 1 * 60 * 60 * 24 // one day

func (s *SmartContract) Register(
	ctx contractapi.TransactionContextInterface,
	id, name, pass string) error {
	user := UserInfo{id, name, pass, ""}
	userJson, err := json.Marshal(user)
	if err != nil {
		return err
	}

	err = ctx.GetStub().PutState(user.UserID, userJson)
	if err != nil {
		return fmt.Errorf("failed to put to world state. %v", err)
	}

	return nil

}

func (s *SmartContract) NewToken(
	ctx contractapi.TransactionContextInterface,
	id, pass string) error {
	userJson, err := ctx.GetStub().GetState(id)
	if err != nil {
		return fmt.Errorf("failed to read from world state: %v", err)
	}

	var userinfo UserInfo
	err = json.Unmarshal(userJson, &userinfo)
	if err != nil {
		return err
	}

	if userinfo.PassWord != pass {
		return fmt.Errorf("user or password err")
	}
	nowtime, _ := ctx.GetStub().GetTxTimestamp()
	// 创建token
	token := Token{
		UserID:     userinfo.UserID,
		ExpireDate: nowtime.GetSeconds() + EXPIRE_DURATION,
	}
	tokenJson, err := json.Marshal(&token)
	if err != nil {
		return fmt.Errorf("failed to create login token %v", err)
	}
	accesstoken := makeSm3Token(&userinfo, nowtime.String())
	// token=> userid + 到期时间
	err = ctx.GetStub().PutState(accesstoken, tokenJson)
	if err != nil {
		return fmt.Errorf("failed to put to world state. %v", err)
	}
	// 为了安全起见，不记录用户的id与token的对应关系，避免所有人可通过id查询到token

	//将token保存到用户信息中
	userinfo.Token = accesstoken
	userJson, err = json.Marshal(&userinfo)
	if err != nil {
		return err
	}
	err = ctx.GetStub().PutState(userinfo.UserID, userJson)
	if err != nil {
		return fmt.Errorf("failed to put to world state. %v", err)
	}

	return nil
}

func (s *SmartContract) CheckToken(
	ctx contractapi.TransactionContextInterface,
	token string) (*Token, error) {
	tokenJson, err := ctx.GetStub().GetState(token)
	if err != nil {
		return nil, fmt.Errorf("failed to read from world state: %v", err)
	}

	var accesstoken Token
	err = json.Unmarshal(tokenJson, &accesstoken)
	if err != nil {
		return nil, err
	}

	if accesstoken.ExpireDate < time.Now().Unix() {
		return nil, fmt.Errorf("token had expire, expire_date is %v, now is %v", accesstoken.ExpireDate, time.Now().Unix())
	}

	return &accesstoken, nil
}

func (s *SmartContract) DeleteToken(
	ctx contractapi.TransactionContextInterface,
	token string) error {

	tokenJson, err := ctx.GetStub().GetState(token)
	if err != nil {
		return fmt.Errorf("failed to read from world state: %v", err)
	}
	if tokenJson != nil {
		return fmt.Errorf("the token %s does not exist", token)
	}

	return ctx.GetStub().DelState(token)
}

func (s *SmartContract) Login(
	ctx contractapi.TransactionContextInterface,
	id, pass string) (*RespLogin, error) {
	userJson, err := ctx.GetStub().GetState(id)
	if err != nil {
		return nil, fmt.Errorf("failed to read from world state: %v", err)
	}
	if userJson == nil {
		return nil, fmt.Errorf("the user %s does not exist", id)
	}

	var userinfo UserInfo
	err = json.Unmarshal(userJson, &userinfo)
	if err != nil {
		return nil, err
	}

	if userinfo.PassWord != pass {
		return nil, fmt.Errorf("user or password err")
	}

	var resplogin RespLogin
	resplogin.Token = userinfo.Token
	resplogin.UserName = userinfo.UserName

	return &resplogin, nil

}

func (s *SmartContract) ChangePass(
	ctx contractapi.TransactionContextInterface,
	id, oldpass, newpass string) error {
	userJson, err := ctx.GetStub().GetState(id)
	if err != nil {
		return fmt.Errorf("failed to read from world state: %v", err)
	}
	if userJson == nil {
		return fmt.Errorf("the user %s does not exist", id)
	}

	var userinfo UserInfo
	err = json.Unmarshal(userJson, &userinfo)
	if err != nil {
		return err
	}

	if userinfo.PassWord != oldpass {
		return fmt.Errorf("user or password err")
	}
	userinfo.PassWord = newpass

	userJson, err = json.Marshal(&userinfo)
	if err != nil {
		return err
	}

	err = ctx.GetStub().PutState(userinfo.UserID, userJson)
	if err != nil {
		return fmt.Errorf("failed to put to world state. %v", err)
	}

	return nil

}

func makeSm3Token(user *UserInfo, nowtime string) string {

	// 使用区块链的时间戳

	data := bytes.Join([][]byte{[]byte(user.UserID),
		[]byte(user.UserName),
		[]byte(user.PassWord),
		[]byte(nowtime),
	}, []byte{})
	return fmt.Sprintf("%x", sm3.SumSM3(data))
}

func main() {
	chaincode, err := contractapi.NewChaincode(&SmartContract{})
	if err != nil {
		log.Panicf("Error creating user chaincode: %v", err)
	}

	if err := chaincode.Start(); err != nil {
		log.Panicf("Error starting user chaincode: %v", err)
	}
}
